home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-08-08 | 99.5 KB | 2,316 lines |
- Ever since I agreed to discuss some aspects (no pun intended <G>) of the
- Windows Aspect programming language here, I have been trying to decide
- on an approach to use. I have now written three opening "lectures", and
- tossed each of them. I just can't seem to find a way to get started
- properly.
-
- There are, of course, some rather major, and many minor, differences in
- how commands operate in the two languages (DOS Aspect and Windows
- Aspect), but pinning these differences down in a "definitive" list is
- not what I plan on doing with this series.
-
- Part of the problem is that the differences between DOS Aspect, covered
- so well already by David and others in the DOS Aspect "course", and
- Windows Aspect, are many, but at times can be quite subtle. A lot of the
- basic difference is a way of thinking, which results from the fact that
- Windows is a multi-tasking environment.
-
- I suspect that the easiest method of doing this course is to simply
- start writing a basic log on script (since I am most familiar with
- PCBoard systems, we'll do it for that type <G>). Since some differences
- in the two languages are based on commands that don't even exist under
- DOS Aspect, we will concentrate the first portion of this "course", on a
- script that uses commands only available in the Windows version of
- Aspect to allow us to check for almost any prompt under certain
- circumstances. Then, once we have a basic log in script done, we'll look
- at multi-tasking under Wasp, using Windows dialog boxes for user input,
- and taking advantage of the unique file format INI to easily store and
- access information.
-
- In the process, we'll also look at the PCP/Win Dialog Editor, what a
- User Window is, and how to use the PCP/Win User Window Builder to design
- one, and some other uniquely Windows features of Wasp.
-
- So, in this "lesson" we'll look at :
-
- - the WHEN TARGET/WAITFOR conflict in Wasp
- - Using TERMGETS to eliminate WAITFOR commands
- - Using STRFIND with TERMGETS
-
-
- The WHEN TARGET / WAITFOR conflict under Wasp
- ---------------------------------------------
-
- Wasp has a couple of advantages over DOS Aspect, one basic one being far
- more possible WHEN commands. However, like DOS Aspect, the most commonly
- used WHEN during a log on (WHEN TARGET INDEX) is limited to just three
- possible targets. As you are probably well aware, during a log on to
- most systems (we'll use PCBoard as an example, since I am most familiar
- with that, and finally build a version of my PCBMail script for handling
- QWK packets on PCBoard systems), there are far more than just three
- possible prompts which can appear. As a result, DOS Aspect programmers
- either have to combine the WHEN TARGET command with WAITFOR commands, or
- continually change the WHEN TARGET commands to account for variations in
- which prompt to look for.
-
- Neither is an ideal solution, especially when you enter the realm of
- Wasp. Here, even more so than in DOS Aspect, combining a WHEN TARGET and
- a WAITFOR can be more troublesome than it is worth. The problem lies in
- the differing nature of the two commands.
-
- Basically, a WHEN allows the script to continue running while it remains
- active. If the target of the WHEN appears, it interrupts the rest of the
- script to perform whatever action is expected of it when that target
- appears. This, of course, makes WHEN TARGET ideal for use to catch
- prompts which only MAY appear, or may appear in various places, rather
- than just once, in sequence, during a log in.
-
- A WAITFOR, on the other hand, does not allow the script to continue
- running while it is active. Everything must halt until either the target
- of the WAITFOR is seen, or the WAITFOR has timed out (since it is a
- timed command, with a default wait of 30 seconds). This makes this
- command fine if you are looking for a prompt which normally ALWAYS
- appears, and generally, does so in the same spot during the log in.
-
- The problem exists, though, in how the WAITFOR operates. As above, the
- rest of the script must stop running while it is active.
-
- THIS INCLUDES THE ACTIONS OF ANY WHEN TARGET THAT IS ACTIVE.
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
- In other words, while a WAITFOR is the active command, a WHEN TARGET
- cannot carry out it's functions, even if it's target is "seen", until
- after the WAITFOR has either been satisfied, or has timed out.
-
- To see how this can affect us, let's look at a sample "scenario". This
- is not a real system, but rather made up prompts to illustrate the
- point, so please don't complain that you've never seen these prompts on
- a PCBoard system <GG>.
-
- You are logging on to this imaginary board. If you have new mail on the
- system, it will tell you so, and then ask if you want to read it now
- (prompt "Read it now [Y/n]? "). However, if you there is no mail for
- you, nothing appears. After the new mail scan, a prompt ("Scan for new
- files [Y/n]?") always appears. Logically, a script segment to handle
- this might be :
-
- when target 0 "Read it now [Y/n]?" call send_no
- waitfor "Scan for new files [Y/n]?" 30
- if success
- transmit "Y^M"
- endif
-
- So... what's the problem here?? Well, since the script continues running
- after the WHEN TARGET is set up, the WAITFOR becomes the active command.
- If you do have new mail, the "Read it now" prompt will appear, and the
- WHEN TARGET will notice it, BUT, because of the nature of the two
- commands, it can not take action based on the target while the WAITFOR
- is active. Instead, it must wait for the WAITFOR to go inactive, in this
- case, since the "Scan for new files" prompt won't appear until after the
- script answers the new mail prompt, for 30 seconds. It will then answer
- the new mail prompt, but the new files prompt will be missed (since the
- WAITFOR timed out.)
-
- Obviously, this is no good. There are a couple of "work around"
- solutions to this. The one which I recommend most often, if you really
- want to continue using the WAITFOR, is as follows :
-
- when target 0 "Read it now [Y/n]?" call send_no
- tries = 1
- while tries < 31
- waitfor "Scan for new files [Y/n]?" 1
- if success
- transmit "Y^M"
- exitwhile
- endif
- tries++
- endwhile
-
- How does this work differently than the earlier version? Well, the
- WAITFOR is only active for 1 second at a time. Once it is inactive, the
- script takes some nanoseconds of time to work through the
- WHILE...ENDWHILE construct. During these nanoseconds, the WHEN TARGET is
- able to interrupt the WHILE...ENDWHILE, and send the response to the new
- mail prompt. Since this brings up the new files prompt, the WAITFOR will
- see it and respond also.
-
- But this routine can be rather difficult to maintain throughout a
- script, since it MUST be maintained for any WAITFOR used while a WHEN
- TARGET is active, in order to have things operate properly. So, is there
- an easier way??
-
-
- Using the TERMGETS command to find prompts
- ------------------------------------------
-
- PCBoard systems actually make things easy for a Wasp script programmer.
- One thing I noticed as I attempted to write a script for a "generic"
- PCBoard system is that virtually ALL PCBoard prompts contain a question
- mark ("?"), usually at the end of the prompt. (Some other types of BBS
- software use a colon (":") to mark the end of a prompt, still others an
- exclamation point ("!")... if you look, you might find a "common" key to
- watch for the system you use most often)
-
- So, it should be possible then, to use a single WHEN TARGET, and watch
- for that "?", then respond to the prompt sent. But how can you determine
- which prompt is sent, and thus, respond correctly to it?
-
- Fortunately, Wasp provides the solution in the form of the TERMGETS
- command, a command not available in DOS Aspect. This little fellow is
- really quite nice. What it does is simple, actually... it reads the line
- on screen (i.e. on the terminal) at a specified row and from a specified
- column position to another specified column position. To accompany it,
- Wasp also provides two system variables that can be used, $ROW, which is
- the current row on the terminal, and $COL, which is the current cursor
- column position on the terminal.
-
- So, if we set up the following command :
-
- termgets $ROW 0 prompt_str $COL
-
- we will have, as the contents of the variable, prompt_str, the text of
- the current row on the terminal, from the beginning (index 0), to the
- current cursor position. Since PCBoard systems always leave the cursor
- on the terminal at the end of the prompt, and waits there for a
- response, this TERMGETS command will return the variable, prompt_str,
- which will be the current prompt that PCBoard is waiting for a response
- to.
-
- Neat, huh? (DOS Aspect programmers... suffer! <GG>)
-
- Using these two "tricks", we can build the beginning of a Wasp PCBoard
- log on script, which will not use any WAITFOR commands at all....
-
- proc main
- when target 0 "?" call get_prompt
- endproc
-
- proc get_prompt
- string prompt_str
- termgets $ROW 0 prompt_str $COL
- endproc
-
- Of course, we still don't do anything with the prompt_str once we have
- it, but first, we have to do one other thing to make the script work for
- us.
-
- Earlier, I said that WHEN TARGET allows the script to continue running
- when it is active, which makes it a multi-tasking command. However, in
- the above segment, this is BAD, as the script will continue running
- right out of the main procedure, and thus, the script. So, believe it or
- not, we have to actually STOP the script from continuing here, until we
- are ready for it to go on. This code will handle that :
-
- integer holding
-
- proc main
- when target 0 "?" call get_prompt
- holding = 1
- while holding
- endwhile
- endproc
-
- proc get_prompt
- string prompt_str
- termgets $ROW 0 prompt_str $COL
- endproc
-
- This stops the script from continuing, as long as the value of holding
- is 1, but since it is through a WHILE...ENDWHILE construct, the WHEN
- TARGET will continue to operate as we want.
-
- The reason that holding is declared as a global variable is that we are
- going to want to be able to change the value of it in later script
- segments, in order to allow the script to continue running when we wish
- it to.
-
- The next step is to respond to the prompts....
-
-
- Applying the STRFIND command to prompt_str
- ------------------------------------------
-
- OK, so this is relatively easy, and I'm sure a lot of you have already
- figured out what to use in order to find out which prompt is on the
- terminal. For those of you who haven't, the command we want is STRFIND.
-
- Simply put, this command searches a string for a second "target" string.
- It can be tested for the system value "found" for the search. This means
- that we can use STRFIND to look for certain key words in the prompt_str
- variable, and based on the "found" system value for that action, take
- certain other actions.
-
- Before we go on, let's discuss the concept of key words.
-
- Many beginning script programmers tend to fall into a wicked trap, which
- can bite back unexpectedly. The trap they fall into is watching for the
- entire prompt from a system, before responding to it. This has many
- drawbacks. Perhaps the two biggest are...
-
- 1) If you are using a board with ANSI color, it is quite possible that
- there will be ANSI codes in any given prompt string. Since WHEN TARGET
- and WAITFOR get "raw" data from the terminal, the presence of ANSI codes
- in a prompt string can result in the script not seeing it as the same as
- the one you are looking for. Since many sysops change these colors
- frequently, the ANSI codes can change, resulting in a failure of your
- script to recognize the prompt.
-
- 2) Many sysops will "customize" the prompts on their system. When they
- do so, they will add things like your first name, etc. using the
- @variables of PCBoard systems, but this will cause problems when you are
- looking for a prompt. A string to look for on one system, may not work
- on the next.
-
- As a result, most Wasp scripters that I know try to watch for a minimal
- amount of text, being only what is absolutely necessary to distinguish
- one prompt from another. Again, PCBoard systems make this fairly easy
- for a scripter, through using "standardized" wording of the prompts sent
- by them.
-
- So, what we want to do is to use STRFIND to locate one of those
- "minimal" key words in the prompt_str variable, and thus, result in a
- response from the script. We'll start with several of the most common
- PCBoard key words....
-
- integer holding
-
- proc main
- when target 0 "?" call get_prompt
- holding = 1
- while holding
- endwhile
- endproc
-
- proc get_prompt
- string prompt_str
- termgets $ROW 0 prompt_str $COL
- if strfind prompt_str "first name"
- transmit $USERID
- transmit "^M"
- elseif strfind prompt_str "Password (Dots"
- transmit $PASSWORD
- transmit "^M"
- elseif strfind prompt_str "Command"
- holding = 0
- endif
- endproc
-
- This is pretty basic, and does not really need the use of a WHEN TARGET,
- but it leads up to the next posting, so we'll let it ride. Basically,
- this script will look for the name prompt, and send the system variable,
- $USERID (which is the user name in the dialing directory entry for this
- system). It also looks for the password prompt, and sends the system
- variable, $PASSWORD (as above, but for the password entry in the dialing
- directory).
-
- The last one is perhaps the one which needs explanation... When the
- script sees the key word "Command", it sets the value of holding to 0,
- but does nothing else. Why? Well, this ends the WHILE...ENDWHILE loop in
- proc main, and exits the script. Since the first place that the key word
- "Command" will appear on a PCBoard system is at the "Main Board
- Command?" prompt, we are finished with our log in, and no longer need
- the script running. So, we set holding to 0, and the script drops
- through the WHILE...ENDWHILE, and stops running.
-
- ------------------------------------------------------------------------
-
- Last time, we wrote a VERY basic log on script for a PCBoard system.
- Today, we're going to work on making it more flexible, and even more
- "generic" (i.e. usable with just about any PCBoard system).
-
- So, our "topics" for today are :
-
- - adding additional prompt key words
- - using multiple key words for a single prompt
- - creating and using custom FUNCTION procedures to eliminate
- some IF/ELSE/ENDIF construct problems
-
-
- Adding additional PCBoard prompts
- ---------------------------------
-
- Obviously, the above script does not account for more than the very
- basic prompts of a PCBoard system. So now, we'll add a few more of the
- more common ones.
-
- Most PCBoard systems will ask you either if you want ANSI graphics, or
- Color graphics. The standard prompt includes the word "graphics", but
- generally, if you are asked if you want Color, the word "graphics"
- doesn't appear in the prompt. For simplicity, we'll say no to graphics
- on these systems (which also avoids concerns about ANSI codes in our key
- word phrases <G>).
-
- So, to cover both possible prompts, we need look for either the key
- word "graphics" or the key word "Color", and then respond with a "N".
- BTW, a lot of people are not aware of it, but you can log on to a
- PCBoard system fairly quickly by responding "N Q NS" to the graphics
- prompt. This tells PCBoard that you don't want graphics ("N"), you want
- a quick log on ("Q"), and you want to skip viewing any NEWS files at log
- on ("NS"). So, that's the response we'll send.
-
- This is the first "real" test of our use of WHEN TARGET, since
- generally, one or the other prompt will appear on most PCBoard systems,
- but not both. So our WHEN TARGET finally comes into use by watching for
- a "possible" prompt, rather than a "probable" prompt.
-
- One other common log on prompt on PCBoard systems is the language
- prompt. This asks which available language you want to use. Again, for
- simplicity, we'll accept the default system language. If you study the
- prompt that generally appears for this, you'll find it normally is
-
- "Language to use (Enter)=no change?"
-
- or a variation thereof, but invariably, ending in "(Enter)=no change?"
-
- But we'll also add several other "common" PCBoard key words. These
- include the "More?" prompt, those which end in "(Enter)=yes?", those
- which end in "(Enter)=no?", and lastly, those which ask if you want to
- "continue?".
-
- ; Windows Aspect PCBoard log on script
-
- integer watchfor ; I've changed this for consistency with my PCBLOG
- ; script, which we'll end up with
-
- proc main
- when target 0 "?" call get_prompt
- watchfor = 1
- while watchfor
- endwhile
- endproc
-
- proc get_prompt
- string prompt_str
- termgets $ROW 0 prompt_str $COL
- if strfind prompt_str "first name"
- transmit $USERID
- transmit "^M"
- elseif strfind prompt_str "Password (Dots"
- transmit $PASSWORD
- transmit "^M"
- elseif strfind prompt_str "=no change"
- transmit "^M"
- elseif strfind prompt_str "graphics"
- transmit "N Q NS^M"
- elseif strfind prompt_str "Color"
- transmit "N Q NS^M"
- elseif strfind prompt_str "Command"
- watchfor = 0
- elseif strfind prompt_str "More?"
- transmit "N^M"
- elseif strfind prompt_str "Enter)=yes?"
- transmit "N^M"
- elseif strfind prompt_str "Enter)=no?"
- transmit "^M"
- elseif strfind prompt_str "continue?"
- transmit "^M"
- endif
- endproc
-
- If you look at the code above, you will notice a certain "inelegance"
- about it, that being that we send the same response to two possible
- STRFIND commands, in several cases. One way to simplify this, is to call
- a procedure to send the response. A simpler way, would be to look for
- either "graphics" OR "Color" in the prompt_str variable.
-
- Since STRFIND is a "found" type of command, I have learned that
- only one of these (or one "success/failure" type) can be tested per IF
- statement. If you try to test two STRFIND commands by using the "||"
- (or) operator, Wasp will give compile errors, about missing tokens (try
- it yourself).
-
- This brings us to another "trick" I use.....
-
-
- Creating and Using a FUNCTION call instead of a Wasp command
- ------------------------------------------------------------
-
- To avoid the compile errors, and yet, still keep the script as simple
- (and "elegant") as possible, we need to test for multiple keywords in a
- prompt_str, key words that all require the same response. But, as noted
- above, we can't do this using STRFIND in an IF statement
-
- In both DOS Aspect, and Wasp, a FUNCTION is a procedure which performs
- operations on various items in a script, and based on the results of
- those operations, "returns" a value (be it numeric or string) back to
- the point where the FUNCTION was called. In effect, the FUNCTION call in
- the original location is replaced by the value that is returned by the
- FUNCTION called.
-
- This is ideal for our problem above, as we would no longer be testing
- for "found", but rather, testing a series of numeric values (if our
- FUNCTION returned a numeric, or integer, value). So basically what we
- want to do, is to pass the key word to the FUNCTION, have it check for
- that key word in the prompt_str, and return an integer value based on
- the result of the STRFIND command.
-
- To do this, we need two things in our script.... a change of the
- declaration of prompt_str from a local variable to a global (so the
- FUNCTION has access to it), and a function to return the value as an
- integer. The function is actually quite simple....
-
- func CheckPrompt:integer
- strparm chk_out
- strfind prompt_str chk_out
- return found
- endfunc
-
- which takes a passed string parameter (our key word), uses STRFIND to
- see if the passed parameter is in the prompt_str variable, and then, in
- effect, replaces the calling statement with the resulting integer,
- indicating found (1) or not found (0). We can place this FUNCTION in our
- script, and call it by using :
-
- CheckPrompt(string)
-
- On big advantage which this FUNCTION offers our script is easy
- modification to include non-standard PCBoard prompts. Suppose that you
- try a new board, and it does not use the string "Password (Dots will
- echo)?" to ask for your password, but rather, merely says "Password?".
- Using the CheckPrompt function, all you have to do is add the new key
- word to the end of the old ELSEIF, and recompile the script, as in :
-
- elseif CheckPrompt("Password (Dots") || CheckPrompt("Password?")
-
- rather than adding an entirely new ELSEIF statement to the script, and
- new lines to send the proper response, in duplication of what is already
- in the script.
-
- Our script would then look like this, if we combined statements using
- the || operator......
-
-
- ; PCBLOG.WAS v.5.2 - 03/23/93
- ; Copyright (c) 1993, Gregg Hommel, All Rights Reserved
-
- integer watchfor, lang = 0, graph = 0
- string prompt_str
-
- proc main
- when target 0 "?" call get_prompt
- watchfor = 1
- while watchfor
- endwhile
- exit
- endproc
-
- proc get_prompt
- termgets $ROW 0 prompt_str $COL
- if chk_prompt("Command")
- watchfor = 0
-
- ; combine the next two lines into one line before compiling
- elseif chk_prompt("Enter)=yes?") || chk_prompt("More?")
- || chk_prompt ("Enter = Yes?")
-
- transmit "N^M"
-
- elseif chk_prompt("=no change?") && lang == 0
- transmit "^M"
- lang++
-
- ; combine the next two lines into one line before compiling
- elseif chk_prompt("Enter)=no?") || chk_prompt("continue?")
- || chk_prompt("=none?") || chk_prompt("Enter = No?")
-
- if chk_prompt("graphics") || chk_prompt("Color?") && graph == 0
- transmit "N Q NS^M"
- graph++
- else
- transmit "^M"
- endif
- elseif chk_prompt("first name")
- transmit $USERID
- transmit "^M"
- elseif chk_prompt("Password (Dots") || chk_prompt("Password?")
- transmit $PASSWORD
- transmit "^M"
- elseif chk_prompt("new user?") || chk_prompt("new caller?")
- transmit "r^M"
- endif
- endproc
-
- func chk_prompt:integer
- strparm chk_out
- strfind prompt_str chk_out
- return FOUND
- endfunc
-
-
- OK, so I tried to slip a couple past you, without explaining them <GG>
-
- # 1 - If line noise, etc. interferes with your log on, in most cases, it
- does little more than cause a resend of the prompt. However, in one
- case, it results in a different prompt being sent, and that is if line
- noise interferes when you send your name.
-
- If PCBoard does not recognize the name, it asks if you want to continue
- as a new user, or resend the name. Since we want to resend the name,
- that is what we respond to the key word for that prompt.
-
- # 2 - As we noted earlier, the graphics prompt generally contains one of
- two key words, and we tested for these in earlier versions of this
- script. However, there is one other "fact" about the graphics prompt...
- it also has the key words "(Enter)=no?", which we have just added to our
- script. The problem is, we don't want to simply reply with an enter, as
- we want to take advantage of the quick log on commands at the graphics
- prompt.
-
- So... after testing for "Enter)=no?", we add another test, this time
- looking for "graphics" or "Color". If we find one those keywords along
- with the "Enter)=no?" key word, we send the quick log commands. If not,
- we send a simple return, and we are now able to vary our response based
- on finding MORE THAN ONE key word in a prompt_str. A further addition to
- this "checking" section is to use a "counter" in it (above we are using
- the variable, graph). On some PCBoard systems, the prompt for graphics
- actually contains two "?", and we use this "counter" to ensure that we
- only send a single response (since the second "?" would look, to the
- script, like a second prompt, and it would respond again, without the
- "counter").
-
- This will be of use next posting, when we attempt to go beyond the "Main
- Board Command?" prompt, and into a mail door.
-
- # 3 - I slipped another "counter" into the response to the language
- prompt. On some PCBoard systems, one of the languages offered includes a
- "?" (i.e. on CRS, one language is listed as "6 - Canadian, eh?" <GG>),
- and this "counter" (the variable, lang) does the same thing here as the
- "counter" in the graphics prompt does... makes sure that we only send a
- single response to the prompt, even if the script spots another "?"
- before it appears.
-
-
- Using INI files - why and how
- -----------------------------
-
- One of the nicest aspects of Windows is the ability of most languages
- under it to access data stored in INI file format. Windows Aspect is no
- exception in this case.... it can easily read and write INI format
- files.
-
- But why do I think these files are so great??
-
- One of the best ways to handle varying configuration and other
- information, is to store it in a file, which is read by your application
- at start up, and allows the application to control various items based
- on the information contained therein. However, on big drawback to this
- is that the application has to read the data from the "configuration"
- file, and then be able to parse the data it has read into useful
- information. All of this takes time, and is quite subject to error (i.e.
- a misplaced space can totally destroy the parsing of a text string into
- usable information.)
-
- The INI file format simplifies things. Basically, the INI format
- consists of a "section" header, contained in square brackets ( [ ] ),
- followed by lines of text in the format
-
- description=information
-
- As a result, there is less chance of errors occurring during editing,
- since, in order to read the information, no parsing of text need occur.
- Each line can be a single bit of information for the application to use,
- and that data need not be retrieved from the configuration file unless
- and until needed by the application (since it is not part of a larger
- text string). Furthermore, the "description" portion of each line can be
- set such that it is fairly easily understood by someone when editing the
- INI file in a standard text editor. As example, if we wanted to store
- our User ID for a system, we could do so like this :
-
- UserID=Gregg Hommel
-
- which, since it is so clearly described, is difficult to "blow" when
- editing an INI file manually. To further explain how simple things can
- be, let's create a file, PCBLOG.INI, in our \WINDOWS subdirectory, using
- NotePad or NDW's DeskEdit or similar.
-
- Now, the reason we want it in the \WINDOWS subdirectory is quite
- simple.... in most languages, if there is no path specified for a file
- with an INI extension, it is assumed to be in the \WINDOWS subdirectory.
- This makes accessing it in a script much simpler (for now... later,
- we'll move it elsewhere, but for a start, we'll put it where Wasp
- expects to find it), and is in itself, another reason why I like using
- INI files... they can be easy to set up and access, since they are
- expected to be in that one place unless a different location is
- specified.
-
- Now, let's create our INI similar to this :
-
- [Canada Remote Systems]
- UserID=Gregg Hommel
- Password=guesswho
- UseLanguage=6
- Graphics=N
- MailCmd=open 67
-
- [PC Board Home]
- UserID=greggy
- Password=whocares
- UseLanguage=1
- Graphics=Y
- MailCmd=open 2
-
- Replace the above with appropriate information for your PCBoard systems,
- of course, but you get the idea. There can be several "sections", each
- one marked by a header in square brackets.
-
- To explain.... the heading (in square brackets) is the "name" you have
- assigned to a system in your dialing directory (the reason for this will
- become obvious later). The rest of the lines are fairly straight
- forward, based on their descriptions, and will be used to extend our log
- on script so that it responds differently, depending on the board you
- are connected to, with the last line being used to extend our script
- beyond the "Main Board Command?" prompt by opening the mail door for
- that system.
-
- But why are we putting the name and password here when they are already
- in the dialing directory entry? Well, two reasons, really... 1) I needed
- some examples to show how an INI can be used <G>, and 2) the fields for
- entries in the dialing directory are limited in size, and many people
- have told me that their name, or system ID will not fit in the field,
- because it is too long (making UserID a prime candidate to use as an
- example).
-
- Using the INI information in our script
- ---------------------------------------
-
- The idea behind using an INI file to store various data for the systems
- that we use our PCBLOG script with, is so that they need not all be
- treated exactly the same way during a log on. One system may be best
- used without graphics (like me, the sysop can't draw a straight line
- without help! <GG>), while another may have the greatest ANSI art during
- log on that you have ever seen. Using an INI file to store data for the
- various systems makes it easy to customize a log on so that it may be
- different depending on the system being accessed.
-
- The first thing we have to do is figure out how to access the
- information in the INI from a script, and this is done through the Wasp
- command, PROFILERD. (Guess what? Some languages refer to an INI file as
- a PROFILE data file. Does the command make more sense now?? <GG>)
-
- Actually, there are two such commands in Wasp, PROFILERD and PROFILEWR.
- I'm sure that I needn't point out that one is used to read from an INI
- file, the other is used to write to one <G> In either case, the format
- of the command syntax is similar :
-
- PROFILERD ININame Section description variable
-
- The variable here can be either text or an integer. For purposes of our
- script, we are going to use only text variables, but one could as easily
- read an integer from an INI file.
-
- As example, let's assign the string variable user_id for our name on any
- given system. This would make the command to read that data for CRS
- above as :
-
- PROFILERD "pcblog.ini" "Canada Remote Systems" "UserID" user_id
-
- Once done, any normal manipulation of the variable thus determined can
- be carried out. Also, any of the above quoted strings can be replaced
- with a variable, including various system variables. And now you can see
- why I wanted the section heading to be identical to the name for the
- system that you have in your dialing directory... that name is
- represented by the system variable $D_NAME, which can be used in the
- above command, as so....
-
- PROFILERD "pcblog.ini" $D_NAME "UserID" user_id
-
- This, of course, means that the script would read the data for whatever
- system we have dialed and connected to. IOW, the script will
- automatically read the information it needs from the correct section of
- the INI file, based on the dialing directory name for whatever system we
- are connected to while the script is running.
-
- To use this in our PCBLOG.WAS file, we need do a couple of things....
-
- 1) at the start of the script, before the main procedure, modify the
- string global declaration to read :
-
- string prompt_str, ini = "PCBLOG.INI", user_id
-
- 2) In the main procedure, after the line which sets the WHEN TARGET, add
- the following line :
-
- set dialdir access $DIALENTRY
- profilerd ini $D_NAME "UserID" user_id
-
- (What this does is to make sure that there is a value for the system
- variable $D_NAME based upon the last dial directory entry accessed, i.e.
- the dialing directory entry which we used to connect to this system, and
- then reads the INI for our user name into the global variable, user_id)
-
- 3) Modify our proc get_prompt section dealing with sending our name from
-
- transmit $USERID
-
- to :
-
- transmit user_id
-
- and our script would send the correct name for whatever system we are
- logged on to.
-
- We needn't go through each change for this, so here is our modified
- PCBLOG.WAS script.....
-
-
- ; PCBLOG.WAS v.5.2a - 03/24/93
- ; Copyright (c) 1993, Gregg Hommel, All Rights Reserved
-
- integer watchfor, lang = 0, graph = 0
- string prompt_str, ini = "PCBLOG.INI"
- string user_id, p_word, lang_num, graph_say
-
- proc main
- when target 0 "?" call get_prompt
- set dialdir access $DIALENTRY
- profilerd ini $D_NAME "UseLanguage" lang_num
- profilerd ini $D_NAME "Graphics" graph_say
- profilerd ini $D_NAME "UserID" user_id
- profilerd ini $D_NAME "Password" p_word
- watchfor = 1
- while watchfor
- endwhile
- exit
- endproc
-
- proc get_prompt
- termgets $ROW 0 prompt_str $COL
- if chk_prompt("Command")
- watchfor = 0
- elseif chk_prompt("Enter)=yes?") || chk_prompt("More?") \
- || chk_prompt ("Enter = Yes?")
- transmit "N^M"
- elseif chk_prompt("=no change?") && lang == 0
- transmit lang_num
- transmit "^M"
- lang++
- elseif chk_prompt("Enter)=no?") || chk_prompt("continue?") \
- || chk_prompt("=none?") || chk_prompt("Enter = No?")
- if chk_prompt("graphics") || chk_prompt("Color?") && graph == 0
- transmit graph_say
- transmit " Q NS^M"
- graph++
- else
- transmit "^M"
- endif
- elseif chk_prompt("first name")
- transmit user_id
- transmit "^M"
- elseif chk_prompt("Password (Dots") || chk_prompt("Password?")
- transmit p_word
- transmit "^M"
- elseif chk_prompt("new user?") || chk_prompt("new caller?")
- transmit "r^M"
- endif
- endproc
-
- func chk_prompt:integer
- strparm chk_out
- strfind prompt_str chk_out
- return FOUND
- endfunc
-
-
- Going beyond the "Main Board Command?" prompt
- ---------------------------------------------
-
- If we add another global variable, and two additional lines to the
- script, we can carry things one step beyond the "Main Board Command?"
- prompt, and one step closer to the script being a mail management
- utility. We need the variable mail_cmd declared as a global string, and
- the following lines added :
-
- 1) In the main procedure, prior to watchfor = 1, add :
-
- profilerd ini $D_NAME "MailCmd" mail_cmd
-
- 2) In procedure get_prompt, change the first section after the termgets
- to :
-
- if chk_prompt("Command")
- transmit mail_cmd
- transmit "^M"
- watchfor = 0
-
- Thus, when the "Main Board Command?" prompt is received, our script will
- send what we have recorded in the INI file as the command needed to open
- the mail door on that system.
-
-
- Using multi-tasking to perform multiple routines
- ------------------------------------------------
-
- Actually, I apologize.... I have decided that this is more appropriate
- in a later "lesson", after we have first built some additional script
- files to prepare for managing the mail and multi-tasked activity.
-
- I also have re-arranged two lessons, # 4 and # 5, as it seems to make
- more sense to discuss dialog boxes, and writing a "set up" script for
- our mail handler, before actually working on the mail handling itself.
-
- So... although we have already taken a quick first look at using INI
- files, in the next "lesson", we'll discuss them further as they apply to
- storing data which we'll want when doing a mail run on a given system
- later.
-
- More discussion of INI files
- ----------------------------
-
- We have already discussed using an INI file to store basic information
- about the systems which we may want to use PCBLOG with, however, an INI
- file can be far more useful than this "basic" data.
-
- When we extend our PCBLOG script into PCBMAIL, there is quite a bit of
- information which we may want to have available for handling the mail,
- and which may change from system to system. Things like the "root" for
- any mail packet for that board, where we might want to store the mail
- from that system, and how we may want to rename it to avoid the PCP/Win
- Zmodem problems which occur when there is already a file in existence in
- your download directory.
-
- The most logical place for all of this information IF you are using
- simply one system for your mail, is as string variables or macros
- defined in the script. However, if you intend using the same script for
- more than one system, and do not want to have to redefine variables, and
- recompile the script under a different name, multiple times, the logical
- place for this "variable" data is in an INI file, where a "generic"
- script can read different values for variables depending upon the system
- logged on to.
-
- Thus, we can use our INI file from our earlier PCBLOG script, to store
- much more information, information which can be variable from system to
- system. However, since we are now going to extend our script into mail
- handling, let's rename it as PCBMAIL.WAS for simplicity sake, and rename
- our INI file as PCBMAIL.INI.
-
- Now, the information which we may need in PCBMAIL.INI is a little more
- extensive than that in PCBLOG.INI. Let's see what it might be....
-
- 1) Every system uses a different "root" name for it's mail packets. One
- system which I use sends me CRS.QWK, while another sends PROPC.QWK. So
- one of the first items needed in our INI would be the "root" name of a
- QWK or REP file for a given system. For this information, we can use the
- simple line :
-
- board_ID=
-
- 2) We already have an entry for the command necessary to open the mail
- door (from PCBLOG.INI), since this also varies from system to system.
- This is :
-
- door_ID=
-
- 3) To avoid problems caused by the implementation of Zmodem used in
- PCP/Win (since Zmodem is by far the most common form of QWK file
- transfer), we should move our mail packets out of our download
- directory, and into a storage directory. This allows us to take our time
- reading a QWK, since we need not worry about it being over-written by
- the next downloaded QWK. So, we would want to store the directory
- information where we might move the QWK to, in our INI. For this, the
- line
-
- mail_dir=
-
- will suffice.
-
- 4) If the script moves a new QWK into our mail storage directory while
- we still have an older QWK there, we have not eliminated the Zmodem
- over-write problem, merely changed when it occurs (i.e. when we move the
- mail to the storage directory). To eliminate it totally, we should also
- rename our mail packets in some fashion as to avoid any possibility of
- overwriting older packets. The simplest form of this is to simply change
- the last character of the extension, starting at A, until Z.
-
- However, on some systems, especially those with a lot of mail, you may
- keep quite a few older packets while you finish reading them. For this
- purpose, perhaps you might want to also incorporate the date of
- downloading into the renaming routine, so that you could easily tell at
- a glance when that file was received.
-
- Let's therefore assume that we may want some system packets renamed in
- the simplest way, and other system's mail renamed to include a date. So,
- we need to store the renaming method in our INI also, in this line :
-
- rename_as=
-
- Do you perhaps begin to see how important and simple an INI file can be
- when used in conjunction with a Wasp script? Since the format is
- predefined, and there are script commands to simply read the data stored
- there (actually, a one line script command will read any single data
- item in an INI file), configuration information, and variable data can
- be easily saved and read from an INI file, without need to parse text,
- or any other method of determining precisely which portion of a file is
- the one we want here.
-
-
- Dialog Boxes - getting user input
- ---------------------------------
-
- So, now we have a script which can read an INI file for variable data
- for each board (even if it does not yet read or use all of the
- information that we have decided to put in the INI <G>), and log on to a
- system based on that data. But... we created the INI file through a text
- editor. Now what we are going to do is to write a script which can be
- used to create that information in the INI file, and for that, we will
- need and use our first simple dialog box.
-
- To do this, let's look at the PCP/Win Dialog Editor.
-
- In a lot of languages (including DOS Aspect), you would have to design a
- dialog box "the hard way"... using various commands for components of
- the dialog box, and sort of guessing at the "position" information
- needed. Then, you would test compile the script to make sure that
- everything was where you wanted, and if not, edit the source, and try
- again. ProComm Plus for Windows makes this all much easier, through the
- Dialog Editor.
-
- Basically, this is a drawing programme, but instead of drawing pictures,
- we use it to "draw" dialog boxes for our scripts. No need to guess at x
- and y coordinates... simply draw the main box, add the components you
- want, stretch them or shrink them to fit and position them where you
- want them, and save the code as a WUD extension file which can be copied
- into your source script.
-
- There are several points to be cautious of, however. The biggest is
- resolution. Always remember that, although you have the perfect design
- for your dialog box, either you, or someone else, will, at some point in
- time, run the script when the video resolution is different from what it
- was when you designed the dialog. Text which fits in an edit box at
- 1024x768 may be barely legible at 640x480. Those nice, tight radio
- buttons at the bottom of the dialog at 640x480 may be spread all over
- the screen at 1024x768.
-
- So...... ALWAYS check any dialog designed for a script with all
- resolutions which you or others may be expected to run!
-
- That being said, let's look at a "sample" segment of code for a dialog
- box. Don't worry about what the variables are, or what will appear in
- the dialog... for now, we are merely interested in discussing the
- make-up of the code. (Actually, this is roughly the code we will use
- when we write the script which we'll use to set parameters for our mail
- manager script, but for now, the contents of the various controls are
- unimportant.)
-
- If you'd like some practice using the Dialog Box Editor, try this....
-
- 1) From this message, copy the code for the dialog box to the Windows
- ClipBoard.
-
- 2) Open the PCP/Win Dialog Box Editor.
-
- 3) From the menu of the Dialog Box Editor, select Edit.... Paste Dialog,
- and the "generic" dialog box represented by this code should appear. If
- you want, try clicking with the mouse in various spots. Notice how the
- information box in the lower right changes information based on where
- you click. Try double clicking on some of the controls to see the
- options available for them. Don't worry, you won't hurt anything, since
- all you will be working with is a "copy" of the original code. (we'll
- discuss more about the Dialog Box Editor, and using the various windows
- and options in a later lesson.)
-
- But now, let's look at the code for our dialog box sample....
-
- First some "rules"....
-
- 1) The "generic" format for all dialog box commands is :
-
- command x-pos y-pos x-width y-height variables
-
- 2) ALL dialog boxes MUST begin with the DIALOGBOX statement and end with
- the ENDDIALOG statement.
-
- 3) In Wasp, anything between these two statements is considered to be
- part of the dialog box. Remember that! You can't put any if...endif
- constructs between the dialogbox...enddialog statements (they are not
- dialog box commands), nor can you put any assign statements, while..
- endwhile sets, or anything else normally used in a script. There can be
- nothing but dialog box commands between the dialogbox... enddialog
- statements!
-
- That said....
-
- dialogbox 80 45 245 186 13
- groupbox 10 4 224 39 shadow
- text 18 12 209 28 center "Use this dialog box to set parameters."
- text 10 51 81 8 right "Select a PCBoard system :"
- combobox 100 49 135 55 pcblist name sort
- text 10 71 60 8 left "QWK filename?"
- editbox 75 69 45 12 board 8
- text 125 70 69 8 left "Mail door # (name)?"
- editbox 195 68 40 13 door
- text 10 91 60 8 left "Mail directory?"
- editbox 75 89 50 12 maildir
- text 130 91 60 8 left "Join Conference?"
- editbox 195 89 40 12 defconf
- groupbox 10 110 225 30 "Rename mail options"
- radiobutton 20 122 100 10 "Standard format - QWA" rb1
- radiobutton 130 122 100 10 "Alternate format - with date" endgroup
- iconbutton 10 147 iconvar pwfile icondx
- iconbutton 40 147 iconvar2 pwfile icondx2
- pushbutton 76 153 40 14 "&Save" normal
- pushbutton 134 153 41 14 "&Delete" normal
- pushbutton 195 153 40 14 "E&xit" cancel
- enddialog
-
- Let's take a look at some of the items above....
-
- This dialog has the following "controls" in it....
-
- groupboxes, text (labels), a combobox, editboxes, radiobuttons,
- iconbuttons, and pushbuttons.
-
- Groupboxes merely "outline" something, with no real "value" in the
- dialog box. Generally, they are used to indicate "sets" of controls,
- which have a related function, task, purpose, or setting. Basically, a
- groupbox can be used to make a dialog look a little prettier, and in
- most cases, be easier to use and/or read.
-
- Text (labels) are exactly what they appear to be... text labels which
- you can place anywhere in a dialog box to inform the user of something,
- be it a description of the contents of another dialog control, or merely
- some informative text.
-
- Combobox... this is a little trickier, and can be quite useful. A
- combobox will display a comma-delimited string variable as a list of
- entries in a drop down box. It appears in the dialog box as a single
- "line" box with a down arrow on the right hand side. Clicking on that
- down arrow will result in a box appearing below the original one,
- containing the contents of a defined, comma-delimited string, one
- "entry" per line. The user can then select one of these entries, which
- is stored in a second string variable. (There is also another control,
- fcombobox, which behaves virtually identically, except that it gets the
- "list" of entries from a file, rather than a string variable. This
- allows for more entries, since string variables in Wasp are limited to
- 255 characters.)
-
- A combobox is an excellent method of offering a user a choice of items,
- without taking up large amounts of space in the dialog box.
-
- Editboxes allow for user input. The user can make text entries into an
- editbox, which are then saved in a string variable. Editboxes are used
- when the string to be entered is an unknown, which prevents offering a
- list to the user.
-
- Radiobuttons, iconbuttons, and pushbuttons are controls which allow
- simple user choices, and can also be used to control the display of the
- dialog box. Radiobutton sets (and they must be in a set of at least two
- buttons), allow the user to make a choice of ONE of several options. His
- choice is mutually exclusive of the other options in the radiobutton
- set, i.e. only one of any set of radiobuttons can be the "active" one at
- any given time.
-
- Iconbuttons and pushbuttons can operate similarly. The only real
- difference is whether or not an icon is displayed on the button, with
- text below it, or the text is displayed right on the button face. Both
- iconbuttons and pushbuttons can be tested to determine which one was
- activated, and some script action performed based on that. One other
- "big" difference is that a "normal" or "cancel" type of pushbutton, when
- depressed, will normally cause the destruction of the dialog box on the
- screen. If this is not desired, then one can use an "update" pushbutton.
-
- So.... what we have above is a relatively simple dialog box for
- obtaining various user input items for the extension of our PCBLOG
- script into a mail handler script. If you paste the above dialog box
- commands into the dialog box editor, you will be able to "see" what the
- dialog box will look like to our user. This procedure, and the commands
- which make up a dialog box in Wasp are far simpler to use, and much more
- powerful than the commands available to DOS Aspect programmers. The
- dialog box editor allows you to "create" your dialogs basically by
- "drawing" them on screen, and then saving the code. As contrast, in DOS
- Aspect, you would write the code, then compile the script to see how the
- box appears on screen. If it isn't the way you want it, then you would
- need rewrite the code, etc. to get it right. In Wasp, the dialog can be
- "designed" to appear correctly before any code is even saved, and that
- code need not even be written, as the dialog box editor will write it
- for you, based on the designed dialog box on the screen of the editor.
-
- One basic to remember about dialog box items is that most of them have
- TWO identifications available. By far the more important identification
- of a dialog box item is it's "value". It is this entry which can be used
- to determine what action has occurred in a dialog box, and thus, what we
- want to happen because of it. Various "types" of controls have different
- values, and each control of that type which is in the dialog has a
- unique value based on that.
-
- As example, let's look at radiobuttons... these have a value of 50 for
- the first group of buttons to appear in the dialog box (when I say
- "appear", I am not referring to on screen appearance, but the
- "appearance" of the radiobutton in the list of dialogbox statements).
- The second group would have a value of 51, the third, 52, etc. Using a
- switch/case construct, we could thus test for a value of $DIALOG which
- matches these, and thus, determine which group of radiobuttons the user
- activated. The "value" for this identification is listed in the
- definition of each dialog box command in the Wasp manual.
-
- The second identification is a "group" identification number, which is
- used to identify ALL dialog box items of that "type". This
- identification is used when one wants to update a dialog to show changes
- in data it displays, say, based on the selection of a radiobutton. To
- continue with our example from above, the identification integer used
- for ALL radiobuttons in a dialog, with the UPDATEDLG command would be a
- 2. The values for this identification are listed in the definition of
- the UPDATEDLG command in the Wasp manual.
-
- Now having to search back and forth in the manual to determine the value
- to be used for each type of command, and for each type of action for
- that command, can get confusing (I know it did for me, at first <G>), so
- I created this "table" summarizing the maximum number of each control
- which can be used in a dialog box, and the two values for each type of
- dialog box command. I have several printouts of the table posted around
- my desk so that I can easily check any value which I need....
-
- -----------------------------------------------------------------
- DIALOG ITEM # of items CONTROL ID UPDATEDLG VALUE
- -----------------------------------------------------------------
- CHECKBOX 32 70 + index 1
- COMBOBOX 16 170 + index 512
- DIRLISTBOX 1 150 1024
- DIRPATH 1
- EDITBOX 16 230 + index 128
- FCOMBOBOX 4 190 + index 32
- FEDITBOX 1 250 8
- FLISTBOX 4 130 + index 16
- FTEXT 1 4
- GROUPBOX 16
- ICON 16 2048
- ICONBUTTON 16 210 + index 4096
- LISTBOX 8 110 + index 256
- PUSHBUTTON *
- normal 16 10 + index
- cancel 1 1
- update 16 30 + index
- RADIOBUTTON ** 8 (16) 50 + index 2
- VTEXT 16 64
-
- * - there are three different types of pushbuttons, normal, update and
- cancel, each with different CONTROL ID values.
-
- ** - there can be a maximum of 8 groups of radiobuttons, each with up to
- 16 radiobuttons in the group.
-
- Any DIALOGBOX item without a CONTROL ID entry indicates that it can
- simply be placed in the dialog box, as a passive control, and can not be
- acted upon in a script. Any item without an entry in the UPDATEDLG
- column indicates that updating of the item is either not possible or
- automatic. Furthermore, some items do not require updating, if they are
- the currently active control, i.e. a combobox will update itself based
- upon the user selection when it is made. One would use the UPDATEDLG
- value for these items only if the script were what changed the value of
- any variables, rather than user interaction.
-
- This time, we're going do things a little differently than we have done
- in earlier lessons. Rather than constantly repeating lines of code
- segments as we "build" our script, and since I already have a script
- written to perform the tasks which we need to store data in the INI file
- for our PCBMail script, what we'll do is post the script in it's
- entirety, with comments (indicated in the normal script fashion, by a
- ";" preceding each line) inserted where appropriate to explain what is
- being done. You may, if you wish, cut and paste from these lessons (with
- or without comments included) in order to "create" the complete
- SETMAIL.WAS script....
-
- ***********************************************************************
-
- ;SETMAIL.WAS v.5.2a - 04/13/93 011:40 AM
- ;Copyright (c) 1993, Gregg Hommel, All Rights Reserved
-
- ; SETMAIL.WAS is a Windows Aspect script for use with ProComm Plus for
- ; Windows 1.01. It is used to set various parameters into an INI file.
- ; PCBMail can then read these parameters to set options when it is run.
-
- integer flag = 0, icondx = 5, icondx2 = 41, ndx = 0, rb1 = 1, source = 0
- string board, defconf, dldir, door, iconvar = "UserID"
- string iconvar2 = "Password", ini = "PCBMAIL.INI", maildir
- string name = "(None)", pcblist = "(None)", pwfile = $PWTASKPATH, pword
- string ren_def, user
-
- ; the above list of global variable declarations shows a personal
- ; preference on my part... they are declared alphabetically. I have
- ; found that this makes it far easier to add a variable later, without
- ; being concerned about duplicating a previously used one. Furthermore,
- ; and I have no proof of this, other than my opinion, but I have found
- ; that my scripts seem to run "better" with my variables declared in
- ; alphabetical order.
- ; It also points out a fact not clearly stated in the manuals... when a
- ; global variable is declared, a default value can be assigned to it
- ; then. If the default value of the variable is a PCP/Win internal
- ; system variable, this too can be assigned when the variable is
- ; declared.
-
- proc main
- integer button, charval, dlgstatus, lendx
- string del_set, pword2, save_set, user2, wrong
-
- ; In our dialog box later, we will need to have a global string which is
- ; the name and path of the PCP/Win executable (which is where we will
- ; find the icons that we'll use <G>). We already have the variable
- ; pwfile assigned the name of the default path for the PCP/Win
- ; directory, i.e. $PWTASKPATH, so now we just use the ADDFILENAME
- ; command to add PW.EXE to that path.
-
- addfilename pwfile "PW.EXE"
-
- ; In many cases, a script may be performing some function or task, but
- ; show nothing visible on the screen to indicate this to a user of that
- ; script. The following dialog box is placed here simply to tell a user
- ; that the script is indeed running. You may find it necessary to insert
- ; dialogs of this type in your scripts, so as to remind yourself that,
- ; although nothing appears to be happening on the screen, in reality,
- ; the script is doing things.
-
- dialogbox 100 75 200 30 13
- text 10 10 180 10 center "PCBMAIL setup is scanning for boards
- now."
- enddialog
-
- ; It would be quite simple to create a string listing the names of the
- ; systems which you call, to be used in our combobox later. However,
- ; once again, we are trying to create a "generic" script which can be
- ; used by anyone. When doing so, we have to remember that the user's
- ; dialing directory might change from time to time. To account for this,
- ; and to make the script as "generic" as possible, we are going to read
- ; each entry in the dialing directory, and if the user has set the
- ; script for that entry as PCBMAIL, we will get the name of the entry,
- ; and add it to our string variable, pcblist, which will be used in our
- ; combobox later. To do this, we use the internal "system" variables,
- ; $DIALCOUNT (the number of entries in the currently active dialing
- ; directory), $D_SCRIPT (the entry in the last accessed dialing
- ; directory entry which lists the script to use with it), and $D_NAME
- ; (the name used in the dialing directory for the last accessed entry).
-
- for ndx = 1 upto $DIALCOUNT
- set dialdir access ndx
- if strcmpi $D_SCRIPT "pcbmail"
- strcat pcblist ","
- strcat pcblist $D_NAME
- endif
- endfor
-
- ; SETMAIL uses a section on the INI for board "(None)" to show sample
- ; settings in the dialog box. However, if this is the first time that
- ; SETMAIL has been run, or if the INI has been deleted, there will be no
- ; section, labeled [(None)]. The easiest way to determine if a section
- ; exists in an INI file, is to read a description that definitely has an
- ; entry, if that section exists. If it is a null string (i.e. if there
- ; is no entry there, then you can safely assume that the section itself
- ; doesn't exist. To this end, we read a description which we know should
- ; exist in [(None)], and if we get a null string, we assume that the
- ; section doesn't exist, and we "create" it.
- ; Note here a couple of points... if a named INI file does not exist,
- ; the first PROFILEWR command issued to that INI name will create the
- ; INI file. Second, if a section in a given INI file does not exist, the
- ; first PROFILEWR command issued which writes to that section will also
- ; automatically add the appropriate section header. This makes using INI
- ; files quite easy, as you do not need to create them, or open them for
- ; reading or writing, or any of the other "normal" file manipulation
- ; needs.
-
- profilerd ini name "board_id" board
- if null_str(board) ; if there is no (None) entry for here
- board = "chanone" ; set default values for it, and write
- door = "e.g. 15" ; them to the INI file
- maildir = "X:\MAILDIR"
- defconf = "e.g. 2"
- write_ini()
- else ; otherwise
- read_ini() ; read the default values for (None)
- endif
- pause 1
- destroydlg
-
- ; Once we have scanned the dialing directory for a list of entries which
- ; are pre-set to use the PCBMAIL script, we then check to see if there
- ; were any! If the pcblist variable contains nothing but the entry
- ; (None) (put there by default), then there are no PCBMAIL systems in
- ; this directory, and we so inform the user, and then shut down this
- ; script.
-
- if strcmpi pcblist "(None)"
- errormsg "There are no PCBMAIL systems in the dialing directory!"
- exit
- endif
-
- ; this next set of code just may look a little familiar <G> This is the
- ; dialog box which we worked with in the previous lesson, now part of
- ; the script it was originally intended for.
-
- dialogbox 80 45 245 186 13
- groupbox 10 4 224 39 shadow
- text 18 12 209 28 center "Use this dialog box to set the \
- parameters which the PCBMAIL script needs to operate. The Join \
- Conference? parameter is optional."
- text 10 51 81 8 right "Select a PCBoard system :"
- combobox 100 49 135 55 pcblist name sort
- text 10 71 60 8 left "QWK filename?"
- editbox 75 69 45 12 board 8
- text 125 70 69 8 left "Mail door # (name)?"
- editbox 195 68 40 13 door
- text 10 91 60 8 left "Mail directory?"
- editbox 75 89 50 12 maildir
- text 130 91 60 8 left "Join Conference?"
- editbox 195 89 40 12 defconf
- groupbox 10 110 225 30 "Rename mail options"
- radiobutton 20 122 100 10 "Standard format - QWA" rb1
- radiobutton 130 122 100 10 "Alternate format - with date" endgroup
- iconbutton 10 147 iconvar pwfile icondx
- iconbutton 40 147 iconvar2 pwfile icondx2
- pushbutton 76 153 40 14 "&Save" normal
- pushbutton 134 153 41 14 "&Delete" normal
- pushbutton 195 153 40 14 "E&xit" cancel
- enddialog
-
- ; if you recall our previous discussion of the identification integers
- ; assigned to the various elements of a dialog box, and the table which
- ; we "created" during that discussion, you will now see the first
- ; implementation of using those identification integers, in particular,
- ; the column labeled CONTROL ID.
- ; When the dialog above first appears, the item "(None)" is the default
- ; selection in the combobox. We have read the default "sample" values
- ; from the INI for (None), and want them displayed, however, we do not
- ; want a user to edit these, since they are only sample values. As a
- ; result, what we want to do is to display those values but disable the
- ; various dialog box elements so that the user can't use them. Once he
- ; has selected a system from the combobox listing, we will re-enable
- ; various controls, but for now, we will use the CONTROL ID integers
- ; from our DIALOG ITEM table to disable some of our dialog.
-
- ; disable the editboxes
- disable ctrl 230 233
-
- ; disable the iconbuttons
- disable ctrl 210 211
-
- ;disable the one group of radiobuttons
- disable ctrl 50
-
- ; disable the first two pushbuttons
- disable ctrl 10 11
-
- ; use the value of $DIALOG to see if anything in the dialog box has been
- ; modified, and act accordingly
-
- dlgstatus = $DIALOG
- while dlgstatus != 1
- switch dlgstatus
-
- ; based upon the CONTROL ID entry in our table, case 170 indicates that
- ; the combobox has been selected in the dialog box, and we thus, handle
- ; the selection of a name from that drop down box combobox. One thing we
- ; have to remember... it is possible that a user might first select a
- ; system from the list, and then later, re-select the (None) entry. Our
- ; code has to handle this contingency, and disable controls again if
- ; (None) is the selected entry.
-
- ; Here also, we see the first use of the other column of entries in our
- ; DIALOG ITEM identification table, i.e. the UPDATEDLG VALUE column.
- ; What this does is quite simple... once you have changed, via the
- ; script, the values for any variables on screen in a dialog box, they
- ; are not changed on screen automatically. Your script has to issue an
- ; UPDATEDLG command, indicating which controls are to be updated on the
- ; screen. The integer value issued with the UPDATEDLG command is the sum
- ; of the individual integer UPDATEDLG VALUE entries from our table.
- ; There is one other "value" which can be used, that being a -1, which
- ; updates ALL controls in the dialog box, without concern for their
- ; individual or cumulative values.
-
- case 170
- if strcmpi name "(None)"
-
- ; if the user selects (None) in the combobox, disable the same controls
- ; as previously, read the default data from the INI file, and then
- ; update the editbox (128) and radiobutton (2) controls in the dialog
-
- disable ctrl 230 233
- disable ctrl 210 211
- disable ctrl 50
- disable ctrl 10 11
- read_ini()
- updatedlg 130
- else
-
- ; otherwise, enable all of those controls previously disabled, read the
- ; INI file data for the chosen system (if any data exists), and then
- ; check to make sure the INI defined mail directory (if there is one) is
- ; valid
-
- enable ctrl 230 233
- enable ctrl 210 211
- enable ctrl 50
- enable ctrl 10 11
- read_ini()
- updatedlg 130
- if not null_str(maildir)
- source = 1
- chk_dir()
- endif
- endif
- endcase
-
- ; cases 210 and 211 handle a press of either iconbutton in the dialog
- ; box. What is unique here is the use of the SDLGINPUT command. In Wasp,
- ; user defined dialog boxes are not the only ones available from a
- ; script. Wasp may also access "standard" dialogs. These are such things
- ; as standard file open and file save dialogs, and the one used here, a
- ; standard input dialog. There are two advantages to using this form or
- ; dialog... 1) they are simple to use, since they do not have to be
- ; "designed" by the scripter, and 2) they CAN and WILL appear over top
- ; of a user defined dialog box. In Wasp, there is only one user defined
- ; dialog box allowed on screen at any given time. By using standard
- ; dialogs, it is possible to get additional user input without
- ; destroying the original dialog box, and then recreating it when needed
- ; again later.
- ; Both procedures work the same way... bring up a standard input dialog,
- ; asking for a "new" value for the appropriate string. Check if the
- ; string exists, and if so, was it changed. If changed, save the new
- ; information.
-
- case 210
- user2=user
- redo:
- sdlginput "UserID" "Enter / Edit the UserID : " user DEFAULT
- if success
- if null_str(user)
- goto redo
- endif
- if not strcmpi user user2
- profilewr ini name "userID" user
- endif
- endif
- endcase
- case 211
- pword2=pword
- again:
- sdlginput "Password" "Enter / Edit the Password : " pword
- DEFAULT
- if success
- if null_str(pword)
- goto again
- endif
- if not strcmpi pword pword2
- profilewr ini name "pword" pword
- endif
- endif
- endcase
-
- ; case 10 takes care of saving the data set in the dialog box. We first
- ; make sure that all the data we need has been entered, then check to
- ; make sure that the mail directory entered is valid, save the data for
- ; the system, and even check the normal download directory for old mail
- ; packets (QW?) for this system, moving them to the mail directory if we
- ; find any.
-
- case 10
- if not (null_str(user) || null_str(pword) || null_str(board) ||\
- null_str(door) || null_str(maildir))
- strlen maildir lendx
- lendx--
- strpeek maildir lendx charval
- if charval == 92
- strdelete maildir lendx 1
- endif
- ren_def = $NULLSTR
- if rb1 == 2
- ren_def = "date"
- endif
- fetch dnldpath dldir
- if null_str(ren_def)
- same_dir()
- if flag
- flag = 0
- exitswitch
- endif
- endif
- source=2
- chk_dir()
- if flag
- flag = 0
- exitswitch
- endif
- strfmt save_set "Save configuration for %s?" name
- sdlgmsgbox "Confirming..." save_set QUESTION YESNO button 1
- if button == 6
- strupr maildir
- strupr board
- strupr defconf
- write_ini()
- is_old_mail()
- statmsg "Configuration for %s saved!" name
- updatedlg 130
- elseif button == 7
- read_ini()
- updatedlg 130
- endif
- else
- wrong=$NULLSTR
- if null_str(user)
- wrong=str_make(wrong, "UserID")
- endif
- if null_str(pword)
- wrong=str_make(wrong, "Password")
- endif
- if null_str(board)
- wrong=str_make(wrong, "Qmail packet name")
- endif
- if null_str(door)
- wrong=str_make(wrong, "Open door")
- endif
- if null_str(maildir)
- wrong=str_make(wrong, "Mail directory")
- endif
- errormsg "Blank Field(s) are not allowed for - %s ." wrong
- endif
- endcase
-
- ; case 11 performs a simple "delete" of a system from PCBMail, by
- removing
- ; all INI file settings. The system is not really deleted, but all
- entries
- ; in the INI are set to null. There is no way, when using an INI file
- ; format to actually delete entries once made, other than via a text
- ; editor, so rather than bother with it, we simply set all entries for a
- ; given system to nothing (null) in order to delete it.
-
- case 11
- if not null_str(name)
- strfmt del_set "Delete configuration for %s?" name
- sdlgmsgbox "Confirming..." del_set STOP YESNO button 1
- if button == 6
- if not null_str(board)
- board = $NULLSTR
- door = $NULLSTR
- maildir = $NULLSTR
- defconf = $NULLSTR
- ren_def = $NULLSTR
- write_ini()
- statmsg "Configuration for %s deleted!" name
- updatedlg 130
- else
- errormsg "%s is not configured!" name
- endif
- endif
- endif
- endcase
- endswitch
- dlgstatus = $DIALOG
- endwhile
- endproc ; end of main procedure
-
- ; a simple procedure which reads the INI file for data which is stored
- there. Used several times, whenever data in MAIN needs updating.
-
- proc read_ini
- profilerd ini name "board_ID" board
- profilerd ini name "userID" user
- profilerd ini name "pword" pword
- profilerd ini name "door_ID" door
- profilerd ini name "mail_dir" maildir
- profilerd ini name "def_conf" defconf
- profilerd ini name "rename_as" ren_def
-
- ; the dialog box for this script does not use a string variable for the
- ; method of renaming a packet, but rather a radiobutton setting. This
- ; little bit of code sets a value for that radiobutton based on whether
- ; the variable ren_def is null or not.
-
- rb1 = 1
- if not null_str(ren_def)
- rb1 = 2
- endif
- endproc
-
- ; another simple little procedure, this time the reverse of above, i.e.
- ; it writes data to the INI file
-
- proc write_ini
- profilewr ini name "board_ID" board
- profilewr ini name "door_ID" door
- profilewr ini name "mail_dir" maildir
- profilewr ini name "def_conf" defconf
- profilewr ini name "rename_as" ren_def
- endproc
-
- ; This function may seem strange, since it does exactly what nullstr
- ; does, however, it does serve a purpose. Windows Aspect only allows the
- ; testing of a single nullstr command in an "if" conditional line. By
- ; using this function to replace it, we can test multiple strings for
- ; nulls, making the if...else conditionals simpler and faster.
-
- func null_str:integer
- strparm test_str
- integer result
- nullstr test_str result
- return result
- endfunc
-
- ; all this function does is take two strings passed to it, and
- ; concatenate them. The trick is that it adds a semi-colon and space to
- ; the resulting string, making the string usable as a variable for our
- ; combobox in the main dialog
-
- func str_make:string
- strparm parm1, parm2
- if not null_str(parm1)
- strcat parm1 "; "
- endif
- strcat parm1 parm2
- return parm1
- endfunc
-
- ; Fairly straightforward.... if standard renaming is used, the mail
- ; directory and default download directory can not be the same. This
- ; proc just checks for that.
-
- proc same_dir
- if strcmpi dldir maildir
- errormsg "If standard renaming is used, the mail and download
- directories
- profilerd ini name "mail_dir" maildir
- updatedlg 130
- flag = 1
- endif
- endproc
-
- ; Again, fairly straightforward... check to see if the mail directory
- ; set in the dialog exists. If not, ask if the user wants it created.
-
- proc chk_dir
- integer button
- string curr_dir, dirmsg
- getdir 0 curr_dir
- if not chdir maildir
- if source == 1
- mkdir maildir
- elseif source == 2
- strfmt dirmsg "The directory %s does not exist. Create it?"
- maildir
- sdlgmsgbox "Warning!" dirmsg EXCLAMATION YESNO button 1 BEEP
- if button == 6
- mkdir maildir
- elseif button == 7
- flag = 1
- profilerd ini name "mail_dir" maildir
- updatedlg 130
- endif
- endif
- endif
- chdir curr_dir
- endproc
-
- ; This proc is real fun stuff! <G> After we have saved the information
- ; for a system, check the download directory for old mail packets. If
- ; one (or more) is found, ask the user what to do with them... either
- ; delete them or rename them according to the settings in the dialog
- ; box. A whole bunch of steps just to do something apparently that
- ; simple, huh?
-
- proc is_old_mail
- integer button, count, test = 0, char, char2, max = 0, ltr, len, rltr
- string oldqwk, msg, newqwk, renqwk, oldfile, root, sdate
- strfmt oldqwk "%s\%s.QW?" dldir board
- if findfirst oldqwk
- strfmt msg "Old mail for %s has been found. Rename it? [NO] will
- delete t
- sdlgmsgbox "Warning!" msg STOP YESNO button 1 BEEP
- if button == 6
-
- ; if we are renaming the mail that was found, we have to determine how
- ; we are going to rename it. If we are using the default scheme (packet
- ; name with a changed extension), things are simple. If we are using the
- ; alternate scheme (up to 4 characters of the packet name, combined
- ; with 4 characters representing the date) then we have to determine
- ; what root we will use for the renamed mail packets
-
- if null_str(ren_def)
- root = board
- else
- substr sdate $DATE 0 5
- strdelete sdate 2 1
- strfmt root "%s%s" board sdate
- strlen root len
- if len > 8
- len -= 8
- strdelete root 4 len
- endif
- endif
-
- ; silly though it may seem, we also check the mail directory to make
- ; sure that, if by some chance there already is mail stored there, we
- ; don't overwrite it when renaming and moving the "new" mail.
-
- strfmt newqwk "%s\%s.QW?" maildir root
- if findfirst newqwk
- max++
- while 1
- if findnext
- max++
- else
- exitwhile
- endif
- endwhile
- endif
- max--
-
- ; now we check to make sure that any packets found in the mail directory
- ; are named properly, and in order. If we find a gap in the extension
- ; (since both renaming sequences use a QW? type of extension) for these
- ; packets in the mail directory, we rename them until they are in proper
- ; order.
-
- for count = 0 upto max
- char = 65 + count
- strfmt newqwk "%s\%s.qw%c" maildir root char
- if findfirst newqwk
- loopfor
- else
- for test upto 25
- char2 = char + test
-
- strfmt newqwk "%s\%s.qw%c" maildir root char2
- if findfirst newqwk
- strfmt renqwk "%s\%s.qw%c" maildir root char
- rename newqwk renqwk
- exitfor
- endif
- endfor
- endif
- endfor
-
- ; and now, we rename and move the mail from the download directory to
- ; the mail directory.
-
- ltr = 65
- rltr = max + 65
- for count = 0 upto 35
- strfmt oldqwk "%s\%s.QW%c" dldir board ltr
- if isfile oldqwk
- rltr++
- strfmt renqwk "%s\%s.QW%c" maildir root rltr
- rename oldqwk renqwk
- if success
- statmsg "%s moved as %s.QW%c" oldfile root rltr
- else
- statmsg "%s not moved!" oldqwk
- endif
- endif
- if count == 25
- ltr = 47
- endif
- ltr++
- endfor
- elseif button == 7
-
- ; If we aren't going to keep the old mail which has been found, this
- ; procedure will delete it.
-
- ltr = 65
- for count = 0 upto 35
- strfmt oldqwk "%s\%s.QW%c" dldir board ltr
- if isfile oldqwk
- delfile oldqwk
- endif
- if count == 25
- ltr = 47
- endif
- ltr++
- endfor
- endif
- endif
- endproc
-
- ***********************************************************************
-
- Before we continue with an examination of the PCBMAIL.WAS script which
- uses the information obtained through SETMAIL.WAS, I think it's
- appropriate to take a small break from actual scripting (before we
- overload on code <G>). So next time, we're going to discuss some tricks
- and tips for writing a script, including some tips on organizing and
- logical structure, and why this kind of planning can assist you when
- writing a script. Then, we'll look at some tricks and tips which involve
- string manipulation under Wasp, and the use of time variables (BTW, did
- I mention that the Wasp SUSPEND UNTIL command does not work properly,
- and we need to develop work-arounds for that fact?? <GG>)
-
- David did a rather nice job of discussing the logic and organizational
- skills necessary for script writing in his course on DOS Aspect, but not
- all of you have seen that course (I am posting this in four additional
- nets, along with those two where David originally posted his DOS
- course).
-
- We won't discuss this too heavily, but I thought that it might be nice
- to take a break from the heavy-duty coding that we have been doing, and
- just relax the tone of things a bit.
-
- Basically, I suppose that every programmer has his own idea of what
- makes up good programming practice. In my opinion, this good practice
- consists of two basic routines.... planning in advance what you want the
- script to accomplish, and roughly how it might do so, and writing the
- code you need in a modular fashion.
-
- Let me explain.....
-
- It is quite difficult to decide what a script (or any code) needs do
- next, when you, the author, have no idea what you want it to do next.
- The code won't "get anywhere" if you, the programmer, have no idea where
- it is that it is expected to go.
-
- Generally, before I begin work on any code, I attempt to write out, in
- English, what I want the code to accomplish, and where I want it to end
- up when it is done. This gives me a basic word picture of what I hope
- the code will do when it is finished. To relate this directly to Wasp,
- it also helps to force you to look at what is happening on the terminal
- with a more careful eye, as you attempt to follow what is happening in
- order to create that word picture.
-
- But... the first rule is to start out simply. Don't get too fancy, and
- don't try to do too much with the first draft of a script. I prefer a
- modular approach to script writing, where one can add features and
- functions simply by adding a new procedure to a basic script. In this
- way, a first draft script can be kept quite simple, and then have other
- routines added to it as they are tested.
-
- However, this does not preclude increasing the size of the main
- procedure. Often, once a procedure has been tested and found to work
- properly, if it is to be called only that one time, I will simplify the
- structure of the script by removing it from a separate procedure, and
- adding it to the main procedure where the sub-procedure was called from
- initially.
-
- Basically, when I begin work on a new script, or upon a new procedure to
- be added to a script, I make sure of two things... 1) that I have lots
- of paper for writing the English, flow chart, and code information, in
- rough, and 2) that I have plenty of disk space available for various
- versions of the code being tested., not to mention backups of it, just
- in case! <GG>
-
- I remember one case where I was away for the weekend, but brought a
- printout of the code I was working on, and a pad of paper to write
- modified code. My daughters complained bitterly that I must have
- destroyed three trees to write that relatively small bit of code,
- because I went through so much paper writing it. But this brings up
- another routine that I use frequently... rather than physically testing
- the code while on line, I often use diagrams, etc. to work out, on
- paper, what should happen, based on the code written, in order to test
- the logic of the script before actually compiling it, and running the
- code.
-
- And that can be supremely important... the logic of the script,
- especially when trying to track down a "bug" or mistaken action. One of
- the main benefits of using English "coding", and flow charting a script
- is that it tends to improve the logic of the script. Rather than
- bouncing here and there, from one procedure to another, all over the
- code, it becomes easier to write the various procedures and routines in
- a more logical fashion. This makes the script easier to follow later,
- when a problem develops, or you want to change some section of it. A
- logical arrangement of sub-procedures and routines within procedures
- makes it far easier to locate a section which you want to change.
-
- To that end, I also attempt to use descriptive variable names, and
- procedure/function names, where ever possible. As example, in my PCB
- Freedom script, the procedure which does the physical dialing of a
- system, and manages the on line functions is called "proc dial_boards".
- The procedure which creates a dialog box to modify system settings while
- off line is called "proc edit_dlgs". The routine to add a new system to
- the configuration list is called "proc add_item".
-
- I am sure that you can see how this might prove advantageous. On a
- little script like our PCBLOG.WAS created earlier, this is not of such
- importance, as the script is fairly short. But PCB Freedom is currently
- around 3,500 lines of Wasp code, and locating a particular section
- within that 3,500 lines can be quite difficult without some "sign
- posts". I use descriptive variable and procedure names, with a logical
- connection to what they are for, as my "sign posts".
-
- I also use the search and replace feature of my editor to my advantage.
- Often times, a variable, or procedure may start life with a particular
- name, descriptive of it's purpose and place within the logic of the
- script. However, as the script takes on new features and functions, that
- descriptive name may no longer be valid, or useful. When this happens,
- "search and replace" allows for easy change of one name or variable in
- use, into another, which may be more informative under the current code
- in use.
-
- There is a further feature of my editor which I use regularly, and one
- that I wonder how anyone who writes scripts can do without... that is
- the MDI interface of the Norton DeskTop Editor. Almost without fail,
- when working on a script, I will have more than one file open at a time.
- Sometimes it is both the WAS file and the LIB (which extension I use to
- indicate that it is an "INCLUDE" file for some WAS file) for it, other
- times it is my actual script and a file containing the code segment that
- I have been working on. With a good MDI interface, cutting and pasting
- from one file to another becomes easy. When I am not using Windows to
- work on my code, I use QEdit, which also allows multiple files open at
- the same time (right now, this file and FREEDOM.WAS are both open in
- QEdit, to make referencing procedures, etc. in Freedom easier for me
- <GG>)
-
- The thing to remember is that there are no "set" rules for methods or
- procedures to write a good script. What works best for one code maven
- may be deadly to another. I know my old CompSci prof from many years
- back would probably have a fit about that comment, as he constantly
- emphasized following rules, etc. when coding, but, in the real world, I
- have found that those "rules" do not always work.
-
- That prof used to always tell us to never use a GOTO label, but to
- always call a sub-routine instead. Theoretically, this may work, but in
- the real world, there are many occasions when you do not want the
- script to return to a given spot, but want it instead to branch off
- through a different set of code. GOTO works rather well for this, while
- calling a sub-routine can be tricky to do the same sort of thing. And
- since Wasp is not Fortran nor Cobol, and I don't run Procomm on a
- mainframe, I don't think that the rules he used to drill into us are
- applicable.
-
- And there is one other thing that I find important about breaking the
- rules... you just might discover something that helps! While working on
- the early versions of GHOST BBS, Toby and I would swap beta code around
- (Toby lives around 100 km. from me... we did everything by modem, most
- often using early drafts of GHOST as the means to transfer modified
- code.), and more times than I care to remember, one or the other of us,
- when discussing changes by voice, would say "But you can't do that!"
-
- The trick is that, sometimes, what conventional wisdom (the rules) says
- can't be done, just might be possible. But you will never discover these
- "huh?" code segments if you follow all of the "rules". In both Freedom
- and GHOST, there are several bits of code which, when I first wrote
- them, were discarded because examination on paper "proved" that I
- couldn't do that. But, when all else failed, or the conventional methods
- grew too cumbersome, I would invariably fall back on the "impossible"
- code, and generally, found that what appeared impossible on paper,
- worked quite well when compiled. <GG>
-
- However, remember that there is a corollary to this "trick"... sometimes
- what appears, at first test, to work, really is "impossible", and can
- bite you when you least expect it. One pitfall to being a "ground
- breaker" is that sometimes, you find that, instead of "breaking new
- ground", you are over the edge of the cliff with an anvil for a
- parachute.
-
- Never discount the "impossible"... fairly early in the beta of Freedom,
- I ran into a problem with some very strange responses being sent by the
- script, when the code was written to delete all characters from the
- string variable, resulting in a null string, and nothing being sent.
- When I discussed it with the folks from Datastorm, I was told that what
- I claimed to have happening was impossible, and that deleting one by
- one, the characters of a string HAD to result in a null string when the
- last character was deleted.
-
- Further testing on my part, and very careful observation (and notes) of
- what was being sent when nothing should have been sent, showed me that,
- somehow, the string, once all characters were deleted, was being
- assigned a value which seemed to be the central six characters from the
- LAST string variable accessed before the current string variable had
- it's last character deleted (does this make sense?? <G>) The solution...
- when the last character was supposed to be deleted from the string,
- instead of actually deleting it, I began assigning the system variable
- $NULLSTR to the string variable. Now, impossible or not, the string
- variable actually was a null when I wanted and expected it to be one.
-
- Datastorm still says the results that I experienced are impossible, but
- I can duplicate them any time I wish by restoring the original code
- segment to that function. I must agree with Datastorm in that,
- logically, the results are absolutely impossible, however, it did
- happen, on my system, and several beta sites.
-
- In other words, never say never, and always suspect that what is not
- possible for a script to do, just may be possible. (and never turn your
- back on a "completed" and "fully tested" script.... they can be mean,
- vicious and downright despicable! <GG>)
-
- I suppose that I have rambled enough for now. I just thought that a
- break from "code" might be nice here, and that telling you of some of my
- experiences with planning and writing scripts might help you establish
- some procedures for writing scripts which work for you....
-
- System Variables
- ----------------
-
- We have already seen and used quite a few "system" variables, such as
- $PASSWORD, $D_NAME, etc. The uses for these pre-defined and reserved
- system variables in a script can be numerous. Some of them contain
- basic operations data for PCP/Win which can be invaluable in a script
- (such as $PWTASKPATH, the directory where PW.EXE, the main ProComm
- executable, is stored, and $ACTIONBAR, which returns the current status
- of the icon bar on screen), others contain information on hardware
- states (such as $DIALING and $FLOWSTATE), information on files ($FATTR
- and $FDATE), and still others hold data on different actions taken by
- scripts, allowing us to check their current status ($FILEXFER, $OBJECT
- and $DIALOG)
-
- Be careful when using some of these, however... some of them do not hold
- a value for long and this can confuse a script. As example....
-
- while 1
- if $DIALOG == 50 || $DIALOG == 30
- do_something()
- destroydlg
- exitwhile
- endif
- endwhile
-
- Logically, this should check to see if the user has accessed either a
- radiobutton or update pushbutton in a dialog, and if they have, call a
- subroutine, destroy the dialog box when we return from that subroutine,
- and then exit the "perpetual loop" setup of the while/endwhile
- construct.
-
- However, pressing the update pushbutton, in this case, would be rather
- disappointing... nothing would ever happen. Why? $DIALOG is a system
- variable which is CLEARED TO 0 AFTER IT IS ACCESSED! Thus, in the above
- code, we "access" $DIALOG, and evaluate the expression "30 == 50" (since
- we pressed an update pushbutton, the value of $DIALOG would be 30),
- which is false. So the IF then evaluates the second expression on the
- line, and also determines that it is false.
-
- "Huh??", you say... "I selected the update button, so the value of
- $DIALOG
- is 30, and the expression should be true. You just said so above!"
- Well, "0 == 30" is false, is it not? And the value of $DIALOG is now 0,
- not 30, since our test for the first expression has already accessed
- $DIALOG, and Wasp has cleared it before we evaluate the second
- expression.
-
- Watch out for this with the following variables...
-
- $DIALOG - cleared to 0 after it is accessed
-
- $FILEXFER - if the value of the variable is either 2 or 3 (a transfer
- completed either successfully, or unsuccessfully), it is reset to 0
- after being accessed.
-
- $MENU - cleared to 0 after it is accessed
-
- $OBJECT - cleared to 0 after it is accessed
-
- $PKRECV - cleared to 0 after it is accessed
-
- $PKSEND - cleared to 0 after it is accessed
-
- The easiest way to avoid this problem is to access the system variable
- only once, when assigning it's value to a user defined (global or local,
- although local works easiest) variable. Then, rather than checking the
- system variable, check the value stored in the user defined variable. As
- example, using the previous code....
-
- while 1
- dlgstat = $DIALOG
- if dlgstat == 50 || dlgstat == 30
- do_something()
- destroydlg
- exitwhile
- endif
- endwhile
-
- This one would work as you would expect.
-
- But there is one additional "type" of system variable which I have
- deliberately not mentioned yet, nor used in the scripts that we have
- been working with. These consist of pre-defined global variables for
- each type of variable possible in a Wasp script, i.e. S0, I0, F0, and
- L0. There are 10 of each type of variable allowed, i.e. S0 through S9,
- etc., but one should use these with caution.
-
- These variables need not be defined in our script, and furthermore, are
- the only variables which can be "passed" from one script to another by
- using the EXECUTE command. They have one additional advantage, being the
- fact that they already exist in memory, and if you are running short of
- memory during a compile of a script, replacing declared global variables
- with one or more of these pre-defined system variables can often regain
- enough free memory to add a few more lines of code (we'll discuss this
- further in a later "lesson")
-
- There are, however, many drawbacks to using these variables....
-
- 1) Since they can be passed to a script called via the EXECUTE command,
- and the value they contain when the child script ends is passed back to
- the parent script, they are too easily inadvertently modified in such a
- way as to lose control of script actions based on their value. If you
- use a user defined global, it's value can not be "passed" to a child
- script, however, likewise, it's value can not be modified inadvertently
- by the child script.
-
- 2) Their names are not exactly descriptive, in keeping with our logic of
- variable names. Thus, six weeks from now, that script which was clear as
- a bell today, may be of absolutely no use to you, since you can't
- remember what the value of I3 is used for later in the script.
-
- 3) They suffer from the basic drawback of all global variables, i.e.
- they can be too easily modified in an obscure procedure, resulting in
- totally unexpected values elsewhere in the script, and totally
- unexpected, and often, inexplicable, actions based on those values.
-
- In the end, there are times when using these predefined globals can be
- quite useful, and the only way to go. When GHOST BBS 2.00 is released,
- it will have the capability to shut itself down at a preset time, and
- run a secondary script. When it does so, it also will pass, to that
- secondary script, either an integer variable, or a string variable, or
- both. Of course, the secondary script will need be written to act on
- those passed variables (PCB Freedom 1.50 will act on the integer, using
- a 1 to tell it to dial the listed systems right away, and when done,
- shut down to return control to GHOST BBS. A 2 passed to Freedom will do
- the same thing, but will eliminate the "System Options" dialog box), but
- the method of passing the variables is, of course, through these
- predefined global system variables, and there is no other way of doing
- so.
-
- Both GHOST BBS and PCB Freedom use other system variables for other
- reasons (mostly to save memory <G>). One method which I have used
- successfully to remind myself of which system variables are in use, and
- what they are being used for, is to add a comment section at the
- beginning of the script to remind me of this information. Something
- which you may want to do, if you use a system variable such as these in
- your scripts.
-
-
- #DEFINE Macros
- --------------
-
- Defined macros have a multitude of uses in a Wasp script. In their
- simplest form, they represent a variable which may have a different
- value for different users of a script, but need be set only once. As
- example, suppose that your script assumes that a user will have the same
- "name" on all systems which he calls. If you were to use a line like
- this :
-
- #DEFINE USERID "Gregg Hommel^M" ; replace with your name and compile
-
- at the beginning of your WAS file, the user could replace the quoted
- text with his name, and then compile the script. In the WAS, any
- appearance of USERID would be replaced by the string which is defined in
- the line above.
-
- This, of course, is perhaps one of the simplest uses of a defined macro.
- Once common one is to make reading your source code more understandable.
- As example, suppose that, throughout your script, you tested quite a few
- variables for a true or false state (false being a value of 0, true
- being a value of 1, for an integer variable). Testing for this is quite
- simple, and can be written as follows :
-
- if my_var == 1 ; if my_var is set to true
-
- or even
-
- if my_var
-
- however, six months from now, would it not be simpler to look at this
- line?
-
- if my_var == TRUE
-
- In my opinion, this is much easier to read, and understand. To
- accomplish a test such as this requires two statements in your script
- prior to the main procedure (i.e. in the section for declaration of
- global variables) :
-
- #DEFINE FALSE 0
- #DEFINE TRUE 1
-
- From then on in the script, every occurrence of the macro FALSE would be
- replaced at compile time, with a 0, and every occurrence of TRUE with a
- 1. The tests would be valid, but the WAS file would be far more
- readable.
-
- We use a similar "trick" in GHOST BBS. Although they are not used all
- that often (actually only about 3 times in two procedures), the
- information stored for mail in GHOST includes data regarding it's
- "public" state, i.e. if it is private, received, etc. These are integer
- values. However, when reading the source code, it is difficult to
- remember that 0 is public, 1 private, etc. etc. So, we used #DEFINE
- macros for those values, and now it is simple. Reading code such as
-
- flag = PRIVATE + NEWMAIL or
- flag = PUBLIC + DELETED
-
- is much easier than remembering what
-
- flag = 3 or
- flag = 4
-
- means <GG>.
-
- A further use for defined macros is to replace frequently used code
- strings. An example of this is from the coding of GHOST BBS. In order to
- improve the speed of screen redraws, one thing that Toby and I did was
- change from scrolling screens to redrawn screens. To do this locally in
- the script is simple.. the Wasp CLEAR command clears a local screen,
- resulting in a redraw of the next screen. However, to accomplish the
- same procedure on a remote user's machine is a touch more complicated.
- What this requires is the transmission to the user's machine of the ANSI
- clear screen command (^L).
-
- In order to increase the speed of displays in GHOST, this command must
- be transmitted frequently (I think the last time I checked, it appeared
- some 250 times in the code), but it is ALWAYS the same command. To
- simplify the code for GHOST, and so that we would not have to remember
- that ANSI code each time we needed it, we placed this line at the
- beginning of the source code for GHOST BBS...
-
- #DEFINE CLS transmit "^L^M" ; send ANSI clear screen to remote
-
- Now, in the source code for GHOST, instead of having to remember what
- the ANSI code for a transmitted clear screen is, we merely remember the
- DOS level command for it, CLS, and put that in the code (all 250 times
- <G>)
-
- There is another use for #DEFINE macros which I like (and I am sure that
- you will think of many other ways to use them <g>), although I have yet
- to use it a great deal. This is conditional compilation....
-
- Although this is not a prime example of a use for this, it will serve to
- point out how it can be used. Let's imagine that you have written a
- script to log onto many different BBS. In one way, it is similar to my
- PCB Freedom script.... the default WAX file includes support for
- encryption of things like passwords in the INI file. The procedures for
- that encryption are proc encrypt and proc uncrypt, which are stored in a
- library, SECURITY.LIB, which, for obvious reasons you can't give out to
- users. Equally as obviously, without that LIB file, they can't compile
- the source code for the rest of the routines themselves.
-
- However, many of the people using your script say that they don't need
- or want that encryption storage, and are quite happy if their passwords
- are stored in clear text. Do you write a separate script for them to use
- if that is the case?
-
- I suppose you could, releasing two versions of the file, one with and
- the other without encryption. But there is another way to do it....
-
- Suppose at the beginning of your source code (the portion which you can
- release to the public), you put something like :
-
- #DEFINE USESEC "yes"
-
- In this case, the actual contents of the macro don't really matter. What
- does matter is the definition itself!
-
- Later in your script, the following code exists.....
-
- #IFDEF USESEC
- #INCLUDE "SECURITY.LIB"
- #ENDIF
-
- and further on, when you read the password from the INI, this code is
- there :
-
- #IFDEF USESEC
- pword = uncrypt(pword)
- #ENDIF
-
- What happens here?? Well, basically, you are telling the script to
- function differently at compile time, and run time, IF a certain macro
- is defined (what it is defined as is immaterial.. only that it is or is
- not defined). In other words, at compile time, IF the macro USESEC is
- defined, include the SECURITY.LIB file during compilation. And, when
- running, if the macro USESEC is defined, decrypt the stored password.
-
- So, how does this "help" your users?? Well, you can issue a WAX file
- based on the macro being defined. It will use your security procedures
- based on those on your system when you compiled it. But, if a user does
- not want to use these "secure" passwords, he can take your "safe" source
- code (i.e. the WAS without the LIB file), comment out the one line
- "#DEFINE USESEC "yes"" and compile the result. It will not look for the
- SECURITY.LIB file, and will not decrypt the password when it reads it.
-
- I know that this is not a very good example, but I think you get the
- drift... the script compiled "conditionally", i.e. if a macro is
- defined, it compiles one way, if the macro is not defined, it compiles
- another way.
-